package control

import (
	"crypto/hmac"
	"crypto/sha1"
	"encoding/base64"
	"errors"
	"fmt"
	"github.com/golang-jwt/jwt/v4"
	log "github.com/sirupsen/logrus"
	"net/http"
	"time"
	"turingApi/conf"
	"turingApi/model"

	"github.com/labstack/echo/v4"
	"github.com/zxysilent/utils"
)

var (
	errMsg string
)

type JwtCustomClaims struct {
	OpenId string `json:"openid"`
	jwt.RegisteredClaims
}

func LoadUser(c echo.Context) (*JwtCustomClaims, error) {
	user := c.Get("user")
	if user == nil {
		errMsg = fmt.Sprintf("user required for JWT authentication")
		conf.Logger.Error(errMsg)
		return nil, errors.New(errMsg)
	}
	userToken := user.(*jwt.Token)
	claims := userToken.Claims.(*JwtCustomClaims)
	return claims, nil
}

func GetShareRecordsHandler(c echo.Context) error {
	ipt := &struct {
		PageNum  int `form:"pageNum" json:"pageNum"`
		PageSize int `form:"pageSize" json:"pageSize"`
	}{}
	err := c.Bind(ipt)
	if err != nil {
		return c.JSON(utils.Fail("输入数据有误", err.Error()))
	}

	jcc, err := LoadUser(c)
	if err != nil {
		return c.JSON(utils.Fail("Load user failed", err.Error()))
	}
	uMod, has := model.UserGetByOpenid(jcc.OpenId)
	if has {
		shareMod, err := model.GetAllShareLogs(uMod.Id, ipt.PageNum, ipt.PageSize)
		if err != nil {
			return c.JSON(utils.Fail("Failed to get all share logs", err.Error()))
		}
		return c.JSON(utils.Succ("succ", shareMod))
	}
	return c.JSON(utils.Fail("User not found", has))
}

func ValidateToken(c echo.Context) error {
	jcc, err := LoadUser(c)
	if err != nil {
		return c.JSON(utils.Fail("Load user failed", err.Error()))
	}

	conf.Logger.Info(fmt.Sprintf("Validate token for openid %s, valid until %s", jcc.OpenId, jcc.ExpiresAt))
	if jcc.VerifyExpiresAt(time.Now(), false) {
		return c.JSON(utils.Succ("token is valid", jcc.ExpiresAt))
	}

	return c.JSON(utils.Fail("token is not valid", jcc.ExpiresAt))
}

func LoginHandler(c echo.Context) error {
	//log.Println("openid", openid, "ok", ok)
	conf.Logger.Info(fmt.Sprintf("User request login, code is %s, header %v", c.Param("code"), c.Request().Header))
	openId, err := getOpenID(c.Param("code"))
	if err != nil {
		conf.Logger.Info(fmt.Sprintf("Get OpenID failed: %v", err))
		//return c.JSON(http.StatusUnauthorized, LoginResult{Data: openId, Success: false})
		return echo.ErrUnauthorized
	}
	conf.Logger.Info(fmt.Sprintf("Successfully exchanged OpenID: %v", openId))

	mod, has := model.UserGetByOpenid(openId)
	if !has {
		conf.Logger.Error(fmt.Sprintf("Creating new user with OpenID %v", openId))
		mod := model.User{Balance: 30, Account: openId, Openid: openId, Ctime: time.Now(), LastIp: c.RealIP()}
		if err := model.UserAdd(&mod); err != nil {
			errMsg = fmt.Sprintf("Failed to add user: %v", err)
			conf.Logger.Error(errMsg)
			return c.JSON(http.StatusInternalServerError, errMsg)
		}
	}

	claims := &JwtCustomClaims{
		OpenId: openId,
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 6)),
		},
	}

	// Create token with claims
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)

	// Generate encoded token and send it as response.
	t, err := token.SignedString([]byte(conf.SecretKey))
	if err != nil {
		errMsg = fmt.Sprintf("SignedString failed: %v", err)
		conf.Logger.Error(errMsg)
		return c.JSON(http.StatusInternalServerError, errMsg)
	}
	conf.Logger.Info(fmt.Sprintf("Successfully generated token %s for OpenID %s valid until %s", t, openId, claims.ExpiresAt))

	return c.JSON(http.StatusOK, echo.Map{
		"data":    t,
		"u":       mod,
		"success": true,
	})

}

// AuthGet doc
// @Tags auth-登陆认证
// @Summary 获取登录信息
// @Param token query string true "凭证"
// @Success 200 {object} model.Reply{data=model.User} "返回数据"
// @Router /adm/auth/get [get]
func AuthGet(ctx echo.Context) error {
	mod, _ := model.UserGet(ctx.Get("uid").(int))
	return ctx.JSON(utils.Succ("auth", mod))
}

// UserLogout doc
// @Tags auth-登陆认证
// @Summary 注销登录
// @Router /api/auth/logout [post]
func UserLogout(ctx echo.Context) error {
	return ctx.HTML(200, "ok")
}

func hmc(raw, key string) string {
	hm := hmac.New(sha1.New, []byte(key))
	hm.Write([]byte(raw))
	return base64.RawURLEncoding.EncodeToString(hm.Sum(nil))
}

// AuthEdit doc
// @Tags auth-登陆认证
// @Summary 修改个人信息
// @Param name formData string true "名称"
// @Param phone formData string true "号码"
// @Param email formData string true "邮箱"
// @Success 200 {object} model.Reply{data=string} "返回数据"
// @Router /adm/auth/edit [post]
func AuthEdit(ctx echo.Context) error {
	ipt := &model.User{}
	err := ctx.Bind(&ipt)
	if err != nil {
		return ctx.JSON(utils.Fail("输入数据有误", err.Error()))
	}
	ipt.Id = ctx.Get("uid").(int)
	if err := model.UserEdit(ipt, "name", "email", "phone"); err != nil {
		return ctx.JSON(utils.Fail("修改失败", err.Error()))
	}
	return ctx.JSON(utils.Succ("succ"))
}

// AuthPasswd doc
// @Tags auth-登陆认证
// @Summary 修改自己的密码
// @Param opasswd formData string true "旧密码"
// @Param npasswd formData string true "新密码"
// @Success 200 {object} model.Reply{data=string} "返回数据"
// @Router /adm/auth/passwd [post]
func AuthPasswd(ctx echo.Context) error {
	ipt := &struct {
		Opasswd string `form:"opasswd" json:"opasswd"`
		Npasswd string `form:"npassws" json:"npasswd"`
	}{}
	err := ctx.Bind(ipt)
	if err != nil {
		return ctx.JSON(utils.Fail("输入数据有误", err.Error()))
	}
	mod, has := model.UserGet(ctx.Get("uid").(int))
	if !has {
		return ctx.JSON(utils.Fail("输入数据有误,请重试"))
	}
	if mod.Passwd != ipt.Opasswd {
		return ctx.JSON(utils.Fail("原始密码输入错误,请重试"))
	}
	mod.Passwd = ipt.Npasswd
	if err := model.UserEdit(mod, "passwd"); err != nil {
		return ctx.JSON(utils.Fail("密码修改失败", err.Error()))
	}
	return ctx.JSON(utils.Succ("succ"))
}

func EditProfileHandler(ctx echo.Context) error {
	if ctx.Get("user") != nil {
		user := ctx.Get("user").(*jwt.Token)
		claims := user.Claims.(*JwtCustomClaims)
		openid = claims.OpenId
	}

	ipt := &struct {
		Nickname string `form:"nickname" json:"nickname"`
	}{}
	err := ctx.Bind(ipt)
	if err != nil {
		return ctx.JSON(utils.Fail("输入数据有误", err.Error()))
	}

	mod, has := model.UserGetByOpenid(openid)
	if !has {
		return ctx.JSON(utils.Fail("用户不存在,请重试"))
	}

	mod.Nickname = ipt.Nickname
	if err := model.UserEdit(mod, "nickname"); err != nil {
		return ctx.JSON(utils.Fail("昵称修改失败", err.Error()))
	}
	if mod.FirstSet && mod.Avatar != "" && mod.Nickname != "" {
		conf.Logger.Info(fmt.Sprintf("User %d first set profile and add balance", mod.Id))
		mod.FirstSet = false
		mod.Balance += 5
		if err := model.UserEdit(mod, "first_set, balance"); err != nil {
			conf.Logger.Error(fmt.Sprintf("Add balance for user %dfailed: %v", mod.Id, err))
			return ctx.JSON(utils.Fail("数据更新失败", err.Error()))
		}
	}
	conf.Logger.Info(fmt.Sprintf("User %d update profile, first update ? %v ", mod.Id, mod.FirstSet))
	return ctx.JSON(utils.Succ("succ", mod))
}

func NotifyShareHandler(ctx echo.Context) error {
	if ctx.Get("user") != nil {
		user := ctx.Get("user").(*jwt.Token)
		log.Println(user)
		claims := user.Claims.(*JwtCustomClaims)
		openid = claims.OpenId
	}
	mod, has := model.UserGetByOpenid(openid)
	if !has {
		return ctx.JSON(utils.Fail("用户不存在,请重试"))
	}
	if !mod.NotifyFlag {
		ipt := &struct {
			SUid int `query:"s" json:"s"`
		}{}
		err := ctx.Bind(ipt)
		if err != nil {
			return ctx.JSON(utils.Fail("输入数据有误", err.Error()))
		}

		if ipt.SUid == mod.Id {
			errMsg = fmt.Sprintf("禁止分享给自己 %d - %d", ipt.SUid, mod.Id)
			conf.Logger.Error(errMsg)
			return ctx.JSON(utils.Fail(errMsg))
		}

		conf.Logger.Info(fmt.Sprintf("分享来源参数: %d, 目标ID: %d", ipt.SUid, mod.Id))
		if ipt.SUid > 0 {
			sMod, has := model.UserGet(ipt.SUid)
			if has {
				conf.Logger.Info(fmt.Sprintf("开始奖励来源用户 %d", ipt.SUid))
				sMod.Balance += 1
				if err := model.UserEdit(sMod, "balance"); err != nil {
					conf.Logger.Error(fmt.Sprintf("加次数失败: %v", err))
					return ctx.JSON(utils.Fail("加次数失败", err.Error()))
				}
				mod.NotifyFlag = true
				if err := model.UserEdit(mod, "notify_flag"); err != nil {
					conf.Logger.Error(fmt.Sprintf("设置标记为true失败: %v", err))
					return ctx.JSON(utils.Fail("设置标记为true失败", err.Error()))
				}
				logMod := model.ShareLogs{SUid: ipt.SUid, TUid: mod.Id, Created: time.Now()}
				if err := model.ShareLogAdd(&logMod); err != nil {
					conf.Logger.Error(fmt.Sprintf("添加分享日志失败: %v", err))
					return ctx.JSON(utils.Fail("设置标记为true失败", err.Error()))
				}
				return ctx.JSON(utils.Succ("succ"))
			}
			return ctx.JSON(utils.Fail("来源用户不存在"))
		}
	} else {
		conf.Logger.Error(fmt.Sprintf("NotifyFlag has been set for target user %d", mod.Id))
		return ctx.JSON(utils.Fail("已奖励"))
	}
	return ctx.JSON(utils.Succ("succ"))
}
